import pandas as pdimport matplotlib.pyplot as pltfrom matplotlib.offsetbox import OffsetImage, AnnotationBboximport seaborn as snsimport matplotlib.pyplot as pltimport seaborn as snsimport itertoolsimport plotting_settingsplotting_settings.set_mpl()from ru_graduates.map_figure import mapFigureimport plotly.io as piopio.renderers.default ="notebook"from IPython.display import display
Данные
Данные о трудоустройстве и зарплатах выпускников российских организаций среднего специального и высшего образования по направлениям подготовки и уровням образования. Данные о трудоустройстве и приведены по состоянию на 31.12.2023, о зарплате — за 12 месяцев 2023 года.
Источник: Роструд, обработка: «Если быть точным»
В датасете нас будут интересовать:
наименование региона или федерального округа
направление подготовки (специализация)
средняя зарплата
пол выпускника
Трудоустройство и зарплаты выпускников по направлениям подготовки//Роструд; обработка «Если быть точным», 2024. Условия использования: Creative Commons BY 4.0. URL: https://tochno.st/datasets/graduates_fields
Код
smaller_df = pd.read_csv('data/data_graduates_specialty_125_v20240709_csv/data_graduates_study_area_125_v20240611.csv', sep=';' )fed_dist_bachelor_df = smaller_df.query('object_level == "Федеральный округ" &''education_level == "Бакалавриат, специалитет" &''gender == "Всего"' )fed_dist_master_df = smaller_df.query('object_level == "Федеральный округ" &''education_level == "Магистратура" &''gender == "Всего"' )print('Пропуски в данных:', '\n')for column, n_empty in fed_dist_master_df.isna().sum().items():if n_empty >0:print(f'Для выпускников магистратуры в данных о зарплате пропущено {n_empty} из {len(fed_dist_master_df)} значений'f' ({n_empty /len(fed_dist_master_df):.0%})' )fed_dist_master_df[fed_dist_master_df['average_salary'].isna()][ ['object_name', 'study_area', 'average_salary'] ].sort_values('study_area', ascending=True).reset_index(drop=True)
Пропуски в данных:
Для выпускников магистратуры в данных о зарплате пропущено 8 из 307 значений (3%)
object_name
study_area
average_salary
0
Южный федеральный округ
Здравоохранение и медицинские науки
NaN
1
Южный федеральный округ
Здравоохранение и медицинские науки
NaN
2
Южный федеральный округ
Здравоохранение и медицинские науки
NaN
3
Приволжский федеральный округ
Здравоохранение и медицинские науки
NaN
4
Уральский федеральный округ
Здравоохранение и медицинские науки
NaN
5
Уральский федеральный округ
Здравоохранение и медицинские науки
NaN
6
Дальневосточный федеральный округ
Здравоохранение и медицинские науки
NaN
7
Северо-Кавказский федеральный округ
Искусство и культура
NaN
Ограничения
В датасете есть информация о выпускниках разных лет — с 2019 по 2023. Размеры выборки не позволяют ограничиться только самыми свежими данными и замерять значения метрик, например, по отдельным федеральным округам и специальностям.
🔎 На карте ниже можно узнать, к каким федеральным округам (ФО) относятся разные области России:
Код
russia_map = mapFigure()import plotly.express as pxregions = pd.read_parquet("data/regions/russia_regions.parquet")fo_list =list(regions['federal_district'].unique())colors = px.colors.qualitative.Pastel1for i, r in regions.iterrows():# popul_text = f"Население: <b>{r.population:_} </b>".replace('_', ' ') text =f'<b>{r.federal_district} ФО<br></b>{r.region}'# ФО<br>{popul_text} russia_map.update_traces(selector=dict(name=r.region), text=text, fillcolor=colors[fo_list.index(r.federal_district)])russia_map.show()
Посмотрим, выпускники каких специальностей зарабатывают больше всего в разных федеральных округаз. В топах по округам представлены выпускники следующих спецальностей:
инженерных и технических
математических и естестеннонаучных
медицинских
общественно-научных
сельскозозяйственных
Код
# Custom formatter functiondef format_yticks(value, _):if value >=1000:returnf'{int(value/1000)}K'else:returnf'{int(value)}'def plot_top_salaries_by_fd(df, no_med=False):# Unique object names to iterate through unique_objects = df['object_name'].unique()# Setting up the grid (2 rows x 4 columns) fig, axes = plt.subplots(2, 4, figsize=(8, 7)) axes = axes.flatten() # Flatten the grid to make iteration easier# Define a color dictionary for the bars color_dict = {'Инженерное дело, технологии и технические науки': "#1E88E5",'Здравоохранение и медицинские науки': "#13B755",'Математические и естественные науки': "#ff0d57",'Сельское хозяйство и сельскохозяйственные науки': "#7C52FF",'Науки об обществе': "#FFC000",'Искусство и культура': "#00AEEF",'Образование и педагогические науки': "C2" } emoji_dict = {'Инженерное дело, технологии и технические науки': 'engineer','Здравоохранение и медицинские науки': 'doctor','Математические и естественные науки': 'nerd','Сельское хозяйство и сельскохозяйственные науки': 'rural','Науки об обществе': 'social','Искусство и культура': 'art','Образование и педагогические науки': 'books' }for ax_index, fed_dist inenumerate(unique_objects):if ax_index >=len(axes):break# In case there are more unique objects than subplots fed_df = df.query(f'object_name == "{fed_dist}"')if no_med: fed_df = fed_df.query('study_area != "Здравоохранение и медицинские науки"' ) fed_stat_df = ( fed_df.groupby('study_area')['average_salary'] .median().nlargest(3) ).reset_index() # Resetting index to get a DataFrame ax = axes[ax_index] # Selecting the corresponding subplotif ax_index notin [0, 4]:for label in ax.get_yticklabels(): label.set_alpha(0.5)# Create a color mapping for hue fed_stat_df['color'] = fed_stat_df['study_area'].map(color_dict)# fed_master_stat_df['color'] = fed_master_stat_df['study_area'].map(color_dict)# Plot with hue and disable legend sns.barplot( x='study_area', y='average_salary', data=fed_stat_df, ax=ax, hue='color', palette=fed_stat_df['color'].tolist(), legend=False, alpha=0.7)# Setting the title for each subplot ax.set_title(fed_dist.replace('федеральный округ', ''), fontsize=16, y=1.05, x=0.7) ax.set_ylim(0, 1.7e5) ax.set_ylabel('') ax.set_xlabel('')# ax.set_xlabel('Специальность')# Overlaying the image on top of each barfor i, bar inenumerate(ax.patches): x0 = bar.get_x() + bar.get_width() /2.0# Center the image horizontally on the bar y0 = bar.get_height() # Position the image at the top of the bar bar_legend = fed_stat_df.iloc[i].study_area image_file =f'{emoji_dict.get(bar_legend)}.png' image = plt.imread(f'data/img/{image_file}') image_box = OffsetImage(image, zoom=0.15) # Adjust zoom as needed ab = AnnotationBbox(image_box, (x0, y0), frameon=False, xycoords='data', box_alignment=(0.5, 0.0)) ax.add_artist(ab)# Turn off the upper and right frame edges ax.tick_params(axis='x', bottom=False)# Apply the custom formatter to the y-axis ax.yaxis.set_major_formatter(plt.FuncFormatter(format_yticks)) ax.set_xticklabels([])# Adding a common text label for the entire figure fig.text(0.5, 0.005, 'Специальность выпускника', ha='center', va='center', fontsize=20, fontweight='bold') fig.text(0.001, 0.45, 'Средняя зарплата (рубли)', ha='center', va='center', fontsize=20, fontweight='bold', rotation=90)# Adjust layout and show the plot plt.tight_layout() plt.show()
Только выпускники инженерных и технических специальностей входят в топ-3 по среднему размеру зарплат во всех федеральных округах
Только в Центральном округе выпускники математических и естестеннонаучных специальностей получают больше остальных
cреди выпускников магистратуры
Наиболее заметный рост зарплаты по отношению к выпускникам бакалавриата или специалитета наблюдается в Центральном и Северо-Западном федеральном округе
Наименьший прирост зарплаты у магистров по сравнению с бакалаврами/специалистами наблюдается в Южном, Приволжском и Северо-Кавказском округах
Аномально большой прирост зарплаты наблюдается у выпускников медицинских специальностей, особенно он заметен в Сибирском и Дальневосточном округах
Магистры медицинских специальностей не входят в топ-3 по уровню зарплат в Южном и Северо-Кавказском округах, скорее всего связано с пропусками в данных
Пропуски в данных
В Южном и Северо-Кавказском округах отсутствует информация о зарплате магистров медицинских наук:
По федеральным округам — надо проверять, но учитывая размер набора, можно предположить, что в ЮФО и СКФО медицинской магистратуры просто нигде нет. Если верить этим товарищам, такая магистратура есть в Москве, СПб, Казани, Томске, Рязани, Тамбове и Владивостоке.
Средние зарплаты выпускников
по стране в целом для выпускников бакалавриата/специалитета и магистратуры:
Среднее значение для зарплаты магистров-медиков выглядит аномально большим. Комментарий Алены Манузиной («Если быть точным»):
Формально медицинская магистратура существует (направления есть в перечне и, например, здесь — все, что с кодами на 32, 33 и 34). Другое дело, что это подготовка скорее к управленческим позициям, поэтому на них идут уже взрослые и опытные люди (это единственная группа со средним возрастом выпускников 35+), отсюда и разница в зарплате между выпускниками бакалавриата/специалитета и магистратуры. Плюс наборы очень маленькие — по данным Роструда это 100-200 человек на всю страну в разные годы, поэтому расчеты на основе этих данных и сопоставление с другими областями образования могут быть очень ненадежными.
⚠︎ Исключим выпускников медицинских специальностей из рассмотрения, поскольку данные об их зарплатах смещены
Больше всего в Росии в среднем зарабатывают выпускники инженерных и технических специальностей (в среднем от 90 до 100 тысяч рублей в месяц)
На втором месте выпускники математических и естественных наук (от 70 до 95 тысяч), на третьем — общественно-научных специальностей (от 60 до 83 тысяч)
Выпускники педагогических, сельскохозяйственных, гуманитарных направлений и специальностей, связанных с искуством и культурой получают примерно одинаково — около 60 тысяч рублей в месяц
Теперь обновим топ специальностей.
Топ-3 специальностей по среднему размеру зарплат по федеральным округам
Из выборки исключены данные о выпускниках медицинских специальностей.
В топах по округам представлены выпускники следующих спецальностей:
Топ-3 специальностей по размеру зарплат в разных федеральных округах для выпускников магистратуры стал гораздо более похож на аналогичное распределение для выпускников бакалавриата/специалитета после исключения смещённых данных о выпускниках медицинских специальностей.
cреди выпускников бакалавриата и специалитета
Только выпускники инженерных и технических специальностей входят в топ-3 по среднему размеру зарплат во всех федеральных округах
Только в Центральном округе выпускники математических и естестеннонаучных специальностей получают больше остальных
cреди выпускников магистратуры
Наиболее заметный рост зарплаты по отношению к выпускникам бакалавриата или специалитета наблюдается в Центральном и Северо-Западном федеральном округе
Наименьший прирост зарплаты у магистров по сравнению с бакалаврами/специалистами наблюдается в Южном, Приволжском и Северо-Кавказском округах
Как меняется зарплата выпускников в зависимости от количества лет с момента выпуска
⚠︎ Здесь мы не будем разделять данные по федеральным округам, так как размеры выборки ограничены. Нужно иметь в виду, что средние зарплаты в разных регионах отличаются.
def plot_career_dynamics(df): df = df.query('study_area != "Здравоохранение и медицинские науки"') grouped_df = df.groupby(['study_area', 'year']).agg( average_salary=('average_salary', 'median'), group_size=('average_salary', 'count') ).reset_index()# Pivot the DataFrame pivot_df = grouped_df.pivot_table( index='study_area', columns='year', values='average_salary' )# Flatten the columns pivot_df.columns = [abs(col -2023) for col in pivot_df.columns]# Reset index to make 'study_area' a column again pivot_df = pivot_df.reset_index() pivot_df = pivot_df.set_index('study_area') _, ax = plt.subplots(figsize=(6, 6)) areas_of_interest = ['Инженерное дело, технологии и технические науки','Математические и естественные науки','Науки об обществе' ] line_styles = ['--', '-.', '-'] line_style_cycle = itertools.cycle(line_styles)for area inlist(pivot_df.index):if area in areas_of_interest: area_label =' и\n'.join(area.split(' и ')) ax.plot(pivot_df.loc[area], label=area_label, ls=next(line_style_cycle))else: ax.plot(pivot_df.loc[area], color='gray', alpha=.2, label='Другие специальности') handles, labels = ax.get_legend_handles_labels() unique =dict(zip(labels, handles))# Define the desired order of the legend entries desired_order = ['Инженерное дело, технологии и\nтехнические науки','Математические и\nестественные науки','Науки об обществе','Другие специальности' ]# Reorder the handles and labels according to the desired order ordered_handles = [unique[label] for label in desired_order if label in unique] ordered_labels = [label for label in desired_order if label in unique] ax.legend(ordered_handles, ordered_labels, fontsize=12, loc='upper left') ax.yaxis.set_major_formatter(plt.FuncFormatter(format_yticks)) ax.grid(alpha=.3) ax.set_xlabel('Время с момента выпуска (годы)') ax.set_ylabel('Средняя зарплата (рубли)') ax.set_xlim(0, 4) ax.set_ylim(40e3, 160e3)return grouped_df_ = plot_career_dynamics(fed_dist_bachelor_df)